define([], function () {
    var $e = {};

    var blobUrl = null;
    if (window.Worker && window.URL && window.URL.createObjectURL) {
        var jsonWorkerSrc = 'self.addEventListener("message", function (e)\
        {\
            var data = e.data;\
            try {\
                var json = JSON.parse( data );\
                self.postMessage( json );\
            } catch (err) {\
                self.postMessage( data );\
            }\
            self.close();\
        }, false );';
        var jsonWorkerBlob = new Blob([jsonWorkerSrc], {type: 'application/javascript'});
        blobUrl = window.URL.createObjectURL(jsonWorkerBlob);
    }
    JSON.parseAsync = function (data, callback) {
        var worker, json;
        if (window.Worker && blobUrl) {
            worker = new Worker(blobUrl);
            worker.addEventListener('message', function (e) {
                json = e.data;
                callback(json);
                worker.terminate();
            }, false);
            worker.postMessage(data);
        } else {
            try {
                json = JSON.parse(data);
                callback(json);
            } catch (e) {
                callback(data);
            }
        }
    };

    var B10000000 = 0x80;
    var B11000000 = 0xC0;
    var B11100000 = 0xE0;
    var B11110000 = 0xF0;
    var B11111000 = 0xF8;
    var B11111100 = 0xFC;
    var B11111110 = 0xFE;
//  var B01111111 = 0x7F;
    var B00111111 = 0x3F;
    var B00011111 = 0x1F;
    var B00001111 = 0x0F;
    var B00000111 = 0x07;
    var B00000011 = 0x03;
    var B00000001 = 0x01;

//test max argument length
    function testMaxArgumentSupport() {
        function testArgs() {
            return arguments.length;
        }

        var lastSuccess = 0;
        var argLen = 0;
        for (var i = 1; i < 32; i++) {
            argLen = (argLen << 1) + 1;
            try {
                testArgs.apply(null, new Array(argLen));
            } catch (e) {
                return lastSuccess;
            }
            lastSuccess = argLen;
        }
        return lastSuccess;
    }

    var MAX_ARG_LEN = testMaxArgumentSupport();
    var canUseTextDecoder = window.ArrayBuffer && window.TextDecoder;
    var decoder = canUseTextDecoder ? new TextDecoder('utf8') : null;

    function utf82strViaTextDecoder(data) {
        if (!decoder) return;
        return decoder.decode(data);
    }

    function utf82str(data) {

        if (data instanceof ArrayBuffer || data instanceof Uint8Array) {
            if (decoder && canUseTextDecoder) {
                var decoded = utf82strViaTextDecoder(data);
                if (decoded !== undefined) return decoded;
            }
        }

        /*
         * every ${BUF_SIZE} UTF8 chars encode into a string fragment
         */
        var length = data.length;
        var BUF_SIZE = Math.min(MAX_ARG_LEN, length);
        var charBuf = new Array(BUF_SIZE);
        var charIdx = 0;
        var fragments = [];
        var fragmentIdx = 0;

        for (var i = 0; i < length;) {
            var c = data[i++];
            var byte = c;
            if (c < 0x80) {
                // result += String.fromCharCode(c);
            } else if ((c < B11100000)) {
                byte =
                    // result += String.fromCharCode(
                    ((B00011111 & c) << 6) |
                    ((B00111111 & data[i++]) << 0)
                // );
            } else if ((c < B11110000)) {
                byte =
                    // result += String.fromCharCode(
                    ((B00001111 & c) << 12) |
                    ((B00111111 & data[i++]) << 6) |
                    ((B00111111 & data[i++]) << 0)
                // );
            } else if ((c < B11111000)) {
                byte =
                    // result += String.fromCharCode(
                    ((B00000111 & c) << 18) |
                    ((B00111111 & data[i++]) << 12) |
                    ((B00111111 & data[i++]) << 6) |
                    ((B00111111 & data[i++]) << 0)
                // );
            } else if ((c < B11111100)) {
                byte =
                    // result += String.fromCharCode(
                    ((B00000011 & c) << 24) |
                    ((B00111111 & data[i++]) << 18) |
                    ((B00111111 & data[i++]) << 12) |
                    ((B00111111 & data[i++]) << 6) |
                    ((B00111111 & data[i++]) << 0)
                // );
            } else if ((c < B11111110)) {
                byte =
                    // result += String.fromCharCode(
                    ((B00000001 & c) << 30) |
                    ((B00111111 & data[i++]) << 24) |
                    ((B00111111 & data[i++]) << 18) |
                    ((B00111111 & data[i++]) << 12) |
                    ((B00111111 & data[i++]) << 6) |
                    ((B00111111 & data[i++]) << 0)
                // );
            }
            charBuf[charIdx++] = byte;
            if (charIdx >= BUF_SIZE) {
                //push char codes to string fragments
                fragments[fragmentIdx++] = String.fromCharCode.apply(String, charBuf);
                charIdx = 0;
            }
        }
        charBuf.length = charIdx;
        //undefined values are ignored when using join
        //String.fromCharCode accepts 0x0000~0xFFFF
        return fragments.join('') + String.fromCharCode.apply(String, charBuf);
    }

    $e.utf82str = utf82str;

//TODO replace by encryptionutils
// private property
    var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

// public method for encoding
    function encode(input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;
        input = _utf8_encode(input);
        while (i < input.length) {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);
            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;
            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }
            output = output +
                _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
                _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
        }
        return output;
    }

    function _utf8_encode(string) {
        string = string.replace(/\r\n/g, "\n");
        var utftext = "";
        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);
            if (c < 128) {
                utftext += String.fromCharCode(c);
            } else if ((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }
        return utftext;
    }

    $e.encode = encode;

    var QUOTE = 34; //"
    var COLON = 58; //:
    var BACKSLASH = 92; //\

    function findDmstArrayBuffer(ab) {
        var i8 = new Uint8Array(ab);

        var found = {
            d: [],
            m: [],
            s: [],
            t: []
        };

        var fullLength = ab.byteLength;
        if (fullLength < 200) {
            searchDmstIn(i8, 0, fullLength, found);
        } else {
            searchDmstIn(i8, 0, 100, found);
            searchDmstIn(i8, fullLength - 100, fullLength, found);
        }

        //check if found is valid
        //quots in payload should be escaped, so there shouldn't be multiple occurances
        if (found.d.length > 1 || found.m.length > 1 || found.s.length > 1 || found.t.length > 1) {
            return;
        }
        var d_i = found.d[0];
        var m_i = found.m[0];
        var s_i = found.s[0];
        var t_i = found.t[0];

        var d_end = fullLength;
        //find minimal just after d_i
        if (m_i && m_i > d_i && m_i < d_end) {
            d_end = m_i;
        }
        if (s_i && s_i > d_i && s_i < d_end) {
            d_end = s_i;
        }
        if (t_i && t_i > d_i && t_i < d_end) {
            d_end = t_i;
        }

        if (d_i > -1 && d_end > -1) {
            var start = d_i + 5;
            var len = d_end - 2 - start;
            if (len >= 0 && d_i + 5 < fullLength) {
                return unescapeJsonString(i8, start, len);
            }
        }
    }

    function unescapeJsonString(i8, start, len) {
        var unescaped = new ArrayBuffer(len);
        var ui8 = new Uint8Array(unescaped);
        var uidx = 0;
        for (var i = start; i < len + start; i++) {
            var b = i8[i];
            if (b === BACKSLASH && i + 1 < i8.length) {
                ui8[uidx++] = i8[i + 1];
                i++;
            } else {
                ui8[uidx++] = b;
            }
        }
        return new Uint8Array(unescaped, 0, uidx);
    }

//start: inc, end: exc
    function searchDmstIn(i8, start, end, out) {
        //tocheck: "x":
        for (var i = start + 3; i < end; i++) {
            var b = i8[i];
            if (b === COLON) {
                if (i8[i - 1] === QUOTE && i8[i - 3] === QUOTE) {
                    var key = String.fromCharCode(i8[i - 2]);
                    if (out[key] !== undefined) {
                        out[key].push(i - 3);
                    }
                }
            }
        }
    }

    $e.findDmstArrayBuffer = findDmstArrayBuffer;

    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// Use a lookup table to find the index.
    var lookup = new Uint8Array(256);
    for (var i = 0; i < chars.length; i++) {
        lookup[chars.charCodeAt(i)] = i;
    }

    function arrayBufferToBase64(arraybuffer) {
        var bytes = new Uint8Array(arraybuffer),
            i, len = bytes.length, base64 = "";

        for (i = 0; i < len; i += 3) {
            base64 += chars[bytes[i] >> 2];
            base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
            base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
            base64 += chars[bytes[i + 2] & 63];
        }

        if ((len % 3) === 2) {
            base64 = base64.substring(0, base64.length - 1) + "=";
        } else if (len % 3 === 1) {
            base64 = base64.substring(0, base64.length - 2) + "==";
        }

        return base64;
    }

    $e.arrayBufferToBase64 = arrayBufferToBase64;

    function tryDecode(str) {
        if (str instanceof ArrayBuffer) {
            str = $e.utf82str(new Uint8Array(str));
        }
        if (str instanceof Uint8Array) {
            str = $e.utf82str(str);
        }
        str = tryParseJson(str);
        return str;
    }

    function tryParseJson(str) {
        try {
            if (typeof str === 'string') {
                return JSON.parse(str);
            }
        } catch (e) {
            // console.error(e);
        }
        return str;
    }

    $e.tryDecode = tryDecode;

    return $e;
});